package ssl;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.ObjectInputStream;
import java.io.OutputStreamWriter;
import java.io.PrintStream;
import java.math.BigInteger;
import java.security.InvalidKeyException;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.NoSuchAlgorithmException;
import java.security.PublicKey;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.RSAPublicKeySpec;
import java.util.Random;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.net.ssl.SSLServerSocket;
import javax.net.ssl.SSLServerSocketFactory;
import javax.net.ssl.SSLSocket;

import rsa.RsaFunktionen;

public class SecureSocket {
	protected static String[] password;
	static String[] encryptedPassword;

	protected static File cPublicKey = new File(
			"src//ssl//serverData//cPublicKey.asc");

	protected static File PublicKey = new File(
			"src//ssl//serverData//sPublicKey.asc");
	static File dbFile = new File("src//ssl//serverData//Database.txt");
	protected static File encPw = new File("src//ssl//serverData//encPw.secure");

	protected static byte[] encBytes;
	static byte[] byteRepresentation;

	public static void init() {
		try {
			RsaFunktionen rsa = new RsaFunktionen();
			KeyPair key = rsa.schluesselErstellen();

			RSAPublicKey rsaPublicKey = (RSAPublicKey) key.getPublic();

			exportRSAPublicKey(rsaPublicKey, PublicKey);

		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	public static void Warten(SSLServerSocket serversocket) {
		init();
		String Name = "";
		try {

			SSLSocket socket = (SSLSocket) serversocket.accept();
			InputStream rawIn = socket.getInputStream();
			BufferedReader in = new BufferedReader(new InputStreamReader(rawIn));
			Name = in.readLine(); // Benutzername, der vom client geschickt wird

			socket = (SSLSocket) serversocket.accept();
			ObjectInputStream is = new ObjectInputStream(
					socket.getInputStream());

			String[] cPublicKeyString = receivePublicKey(is);

			socket = (SSLSocket) serversocket.accept();
			FileInputStream fis = new FileInputStream(PublicKey);
			DataOutputStream dos = new DataOutputStream(
					socket.getOutputStream());

			sendPublicKey(dos, fis);

			socket = (SSLSocket) serversocket.accept();
			password = generatePasswords(1, 8);
			writeIntoDatabase(Name); // or create a Database.txt if it doesn't
										// exist

			byte[] dataBytes = password[0].getBytes();

			String xform = "RSA";
			encBytes = encrypt(dataBytes,
					generateRSAPublicKey(cPublicKeyString), xform);

			byteArrayToFile(encPw);

			FileInputStream fis1 = new FileInputStream(encPw);
			DataOutputStream dos1 = new DataOutputStream(
					socket.getOutputStream());
			sendEncryptedPw(dos1, fis1);

			if (encPw.exists())
				encPw.delete();

		} catch (Exception ex) {
			ex.printStackTrace();
			System.out
					.println("Die Verbindung zum Client wurde unterbrochen! Warte auf neue Verbindung!");

		}
	}

	public static void byteArrayToFile(File file) throws FileNotFoundException,
			IOException {

		FileOutputStream fileOutputStream = new FileOutputStream(file);
		fileOutputStream.write(encBytes);
		fileOutputStream.close();

	}

	public static void writeIntoDatabase(String Name) {
		boolean append = false;
		try {
			if (dbFile.exists())
				append = true;
			else
				append = false;

			BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(
					new FileOutputStream(dbFile, append)));
			bw.write(Name + " \n" + password[0]);
			bw.newLine();
			bw.flush();

		} catch (IOException ex) {
			System.out.println(ex);
		}
	}

	public static void exportRSAPublicKey(RSAPublicKey rsaPublicKey, File file)
			throws Exception {
		PrintStream out = new PrintStream(new FileOutputStream(file));
		String mod = rsaPublicKey.getModulus().toString(16);
		String exp = rsaPublicKey.getPublicExponent().toString(16);
		out.print(mod + "\n" + exp);
		out.close();
	}

	public static void exportRSAPrivateKey(RSAPrivateKey rsaPrivateKey,
			File file) throws Exception {
		PrintStream out = new PrintStream(new FileOutputStream(file));
		String mod = rsaPrivateKey.getModulus().toString(16);
		String exp = rsaPrivateKey.getPrivateExponent().toString(16);
		out.print(mod + "\n" + exp);
		out.close();
	}

	public static RSAPublicKey generateRSAPublicKey(String[] pubKey) {
		String mod = pubKey[0];
		String exp = pubKey[1];
		RSAPublicKey rsaPublicKey = null;
		try {
			RSAPublicKeySpec rsaPublicKeySpec = new RSAPublicKeySpec(
					new BigInteger(mod, 16), new BigInteger(exp, 16));
			KeyFactory keyFactory;

			keyFactory = KeyFactory.getInstance("RSA");

			rsaPublicKey = (RSAPublicKey) keyFactory.generatePublic(rsaPublicKeySpec);
		} catch (NoSuchAlgorithmException e) {
			e.printStackTrace();
		} catch (InvalidKeySpecException e) {
			e.printStackTrace();
		}
		return rsaPublicKey;
	}

	public static byte[] encrypt(byte[] inpBytes, PublicKey key,
			String password) {
		Cipher cipher;
		try {
			cipher = Cipher.getInstance(password);
			cipher.init(Cipher.ENCRYPT_MODE, key);
			return cipher.doFinal(inpBytes);
		} catch (NoSuchAlgorithmException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (NoSuchPaddingException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (InvalidKeyException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IllegalBlockSizeException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (BadPaddingException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return null;
		
	}

	// still its not neccesary to decrypt on Server
	// private static byte[] decrypt(byte[] inpBytes, PrivateKey key,
	// String password) throws Exception{
	// Cipher cipher = Cipher.getInstance(password);
	// cipher.init(Cipher.DECRYPT_MODE, key);
	// return cipher.doFinal(inpBytes);
	// }

	public static void sendEncryptedPw(DataOutputStream dos, FileInputStream fis)
			throws IOException {
		int j = 0;
		while ((j = fis.read()) != -1) {
			dos.write(j);
		}
		dos.flush();
		dos.close();
		fis.close();

	}

	public static void sendPublicKey(DataOutputStream dos, FileInputStream fis)
			throws IOException {
		int j = 0;
		while ((j = fis.read()) != -1) {
			dos.write(j);
		}
		dos.flush();
		dos.close();
		fis.close();

		System.out.println("sPublicKey zum Client transferiert!");
	}

	public static String[] receivePublicKey(ObjectInputStream is)
			throws IOException {
		String[] retval = new String[2];
		BufferedReader br = new BufferedReader(new InputStreamReader(is));

		retval[0] = br.readLine();
		retval[1] = br.readLine();

		br.close();

		System.out.println("cPublicKey erhalten");

		return retval;
	}

	public static String[] generatePasswords(int amount, int length) {
		String[] passwords = new String[amount];
		Random random = new Random();
		for (int i = 0; i < amount; i++) {
			passwords[i] = generatePassword(length, random);
			System.out.println(passwords[i]);
		}
		return passwords;
	}

	public static String generatePassword(int length, Random random) {
		StringBuffer buffer = new StringBuffer(length);
		for (int i = 0; i < length; i++) {
			buffer.append(getRandomChar(random));
		}
		return buffer.toString();
	}

	public static char getRandomChar(Random random) {
		int alphanumeric = random.nextInt(3);
		int range = 0;
		int toAdd = 0;

		switch (alphanumeric) {
		// numeric
		case 0:
			range = 10;
			toAdd = 48;
			break;
		// uppercase
		case 1: {
			range = 26;
			toAdd = 65;
			break;
		}
			// lowercase
		case 2: {
			range = 26;
			toAdd = 97;
			break;
		}
		} // get char in range
		int randomChar = random.nextInt(range);

		randomChar += toAdd;
		return (char) randomChar;
	}

	public static void main(String[] args) {
		System.setProperty("javax.net.ssl.keyStore", "myKeystore");
		System.setProperty("javax.net.ssl.keyStorePassword", "123456");

		try {
			SSLServerSocketFactory factory = (SSLServerSocketFactory) SSLServerSocketFactory
					.getDefault();
			SSLServerSocket serversocket = (SSLServerSocket) factory
					.createServerSocket(5432);
			Warten(serversocket);
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
}
